%!Tex Program = xelatex
\documentclass[a4paper]{article}
\usepackage{xltxtra}
\usepackage{graphicx}
\usepackage{fontspec}
\usepackage{cite}
\usepackage{geometry}
\usepackage{amsmath}
% 设置中文字体
\setmainfont{AR PL UMing CN}

\geometry{left=20mm,right=20mm,top=20mm,bottom=20mm}

\begin{document}


\title{final homework}
\author{zza}
\date{\today}
\maketitle

\section{介绍}

请注意\\

这里展示的材料也在第10课视频中讨论过。(所有的视频讲座也可以在这里找到。)\\
基本建立了有限元方法\\
这是第一个用有限元来计算的例子。我们将解一个简单版本的泊松方程，它的边值为零，但右边是非零的:

\begin{equation}
    -\Delta u = f        in \Omega
\end{equation}
\begin{equation}
    u = 0        on \partial\Omega
\end{equation}

我们将在正方形上求解这个方程，$Ω=[−1,1]^2$，对于这个方程，您已经学习了如何在步骤1和步骤2中生成网格。\\
在本程序中我们也将只考虑特殊情况f(x)=1，并在下一个教程程序步骤4中讨论如何实现更一般的情况。\\
如果你学过有限元法的基础知识，你会记得我们需要采取的步骤，用有限维近似来近似解u。具体地说，我们首先需要\\
推导出上面方程的弱形式，我们通过从左边乘以一个测试函数$φ$得到它(我们将回到从左边乘以而不是从右边乘以的原因)，\\
并在定义域$Ω$上积分:

\begin{equation}
    -\int_\Omega \varphi \Delta u = \int_\Omega \varphi f   
\end{equation}
这可以通过部分积分:
\begin{equation}
    -\int_\Omega \nabla\varphi \cdot\nabla u = \int_\Omega \varphi f  
\end{equation}
测试函数$φ$必须满足相同的边界条件(用数学术语来说:它需要来自我们寻求解的集合的切空间)，因此在边界$φ=0$上，因此我们\\
正在寻找的弱形式读取
\begin{equation}
    (\nabla\varphi , \nabla u) = (\varphi,f)
\end{equation}
这里我们使用了常用的符号$(a,b) = \int_{\Omega} ab$。然后，问题要求一个函数u，对于来自适当空间(这里是空间$H^1$)的所有测试函数$φ$，该语句为真。
当然，在一般情况下，我们无法在计算机上找到这样的函数，相反，我们寻求一个近似值$u_h(x)=\sum_{j}U_j\varphi_j(x)$，其中$U_j$是我们
需要确定的未知膨胀系数(这个问题的“自由度”)，$φ_i(x)$是我们将使用的有限元形状函数。要定义这些形状函数，我们需要以下内容:\\



用来定义形状函数的网格。你已经看到了如何在步骤1和步骤2中生成和操作描述网格的对象。\\

描述我们想要在参考单元上使用的形状函数的有限元素。II总是单位间隔$[0,1]$，单位平方$[0,1]^2$或单位立方$[0,1]^3$，这取决于你在哪个空间维度中
工作)。在步骤2中，我们已经使用了$FE_Q<2>$类型的对象，它表示通过在支撑点上插值来定义形状函数的常见拉格朗日元素。最简单的是$FE_Q<2>(1)$，
它使用多项式次数为1。在2d中，这些通常被称为双线性，因为它们在参考单元的两个坐标中都是线性的。(在一维中，它们是线性的，在三维中是三线
性的;然而，在这笔交易中。在II文档中，我们通常不会做出这种区分，而总是简单地称这些函数为“线性”。

一个DoFHandler对象，以有限元对象提供的参考单元描述为基础，枚举网格上的所有自由度。您也已经在步骤2中看到了如何做到这一点。

一种映射，说明如何从参考单元格上的有限元类定义的形状函数获得真实单元格上的形状函数。默认情况下，除非您明确表示不这样做，否则交
易。我将使用(双-，三-)线性映射，所以在大多数情况下你不必担心这一步。\\
通过这些步骤，我们现在有了一组函数$φ_i$，我们可以定义离散问题的弱形式:找到一个函数$u_h$，即找到上面提到的展开系数$U_j$，使

\begin{equation}
    (\nabla\varphi _i,\nabla u_h)=(\varphi_i,f),i=0  \ldots N−1.
\end{equation}
请注意，这里我们遵循从0开始计数的惯例，这在C和c++中很常见。这个方程可以被重写为一个线性系统如果你插入表示$u_h(x)=\sum_jU_j\varphi_j(x)$然后观察它
\[
\begin{aligned}
(\nabla\varphi_i,\nabla u_h) & = (\nabla\varphi_i,\nabla[\sum_JU_j\varphi_j]) \\
& = \sum_j(\nabla\varphi_i,\nabla[U_j\varphi_j]) \\
& =\sum_j(\nabla\varphi_i,\varphi_j)U_j .
\end{aligned}
\]
有了这个，问题是:找到一个向量U，使
\[AU = F,\]
矩阵A和右边的F被定义为
\[
A_(ij) =(\nabla\varphi_i,\nabla\varphi_j)
F_i = (\varphi_i,f).
\]
我们应该从左边还是从右边乘以一个测试函数?\\
在我们继续描述如何计算这些量之前，请注意，如果我们从右边乘以一个测试函数而不是从左边乘以原始方程，那么我们将得到一个线性系统的形式
\[
    U^TA=F^T
\]

用一个行向量$F^T$，通过转置这个系统，这当然等于解

\[A^TU=F\]
这里和上面一样，因为$A=A^T$。但一般情况下不是这样，为了避免任何混乱，经验表明，只要养成从左边乘方程的习惯，而不是从右边乘方程(就像数学文献中经常做的
那样)，就可以避免一类常见的错误，因为矩阵是自动正确的，在比较理论和实现时不需要转置。在本教程的第一个例子中，我们有一个非对称双线性形式，从右乘还是从左乘是有区别的。\\
组合矩阵和右边的向量\\
现在我们知道我们需要什么(即:保存矩阵和向量的对象，以及计算$A_(ij),F_i$的方法)，我们可以看看实现这一目标需要什么:\\

A的对象类型为SparseMatrix，而U和F的对象类型为Vector。我们将在下面的程序中看到用于求解线性系统的类。
我们需要一种形成积分的方法。在有限元法中，最常用的方法是使用正交，即将积分替换为每个单元上的一组正交点的加权和。也就是说，我们首
先把$Ω$上的积分分解成对所有单元格的积分，
\[
A_ij=()\nabla\varphi_i,\nabla\varphi_j)= \sum_{k \in T}\int_k\nabla\varphi_i \cdot\nabla\varphi_j
F_i=(\varphi_i,f)=\sum_{K \in T}\int _K\varphi_if    
\]
然后通过求积分来近似每个单元的贡献
\[
    A_(ij)^K = \int \nabla\varphi_i \cdots\nabla\varphi_j \approx\sum_q\nabla\varphi_i(x_q^K)f(x_q^k)w_q^k
\]
式中$T\approx\Omega$为近似域的三角剖分，$x_q^k$为单元格K上的第q个正交点，$w^K_q$为第q个正交权值。这样做需要不同的部分，下面我们将依次讨论它们。

首先，我们需要一种方法来描述交点的位置$x^K_q$和它们的权重$w^K_q$。它们通常以与形状函数相同的方式从参考单元映射，即隐式地使用MappingQ1类，或\\
者，如果您显式地这样说，通过从Mapping派生的其他类之一进行映射。参考单元格上的位置和权重由派生自Quadrature基类的对象描述。通常，人们\\
选择一个正交公式(即一组点和权值)，使正交恰好等于矩阵中的积分;这可以实现，因为积分中的所有因子都是多项式，并且是通过高斯正交公式完成的，在\\
QGauss类中实现。然后我们需要一些可以帮助我们评估$φ(x^K_q)$细胞K .这是什么FEValues类:需要一个有限元对象描述$φ$参比电池,交对象来描述正交分\\
和权重,和一个映射对象(或隐式地将MappingQ1类),并提供价值和衍生品对实体单元形状函数的K以及各种集成所需的其他信息,在正交分位于K。\\
 
计算矩阵和右手边作为对所有单元求和(然后对正交点求和)的过程通常称为线性系统的组装，或简称组装，使用与装配线相关的单词的含义，意思是“将一组
碎片，片段或元素组合在一起的行为”。fvalues实际上是汇编过程中的中心类。你可以这样看待它:有限元素及其派生类描述形状函数，即无限维对象:函数
在每个点上都有值。理论上我们需要这个因为我们想用函数上的积分进行分析。然而，对于计算机来说，这是一个非常困难的概念，因为它们通常只能处理
有限数量的信息，因此我们通过使用在参考单元(正交对象)上定义的点映射(映射对象)到实际单元上的点来获得对正交点的求和来替换积分。从本质上讲，
我们将问题简化为只需要有限数量的信息，即形状函数值和导数，正交权值，法向量等，只在有限的一组点上。fvalues类将这三个组件组合在一起，并提
供关于特定单元格k的有限信息集。当我们组装下面的线性系统时，您将看到它的作用。\\


值得注意的是，如果您只是在应用程序中自己创建这三个对象，并自己处理信息，也可以实现所有这些目标。然而，这既不会更简单(fevalvalues类提供了
你真正需要的那种信息)，也不会更快:fevalvalues类被高度优化，只在每个单元格上计算你需要的特定信息;如果可以重用前一个单元格中的任何内容，那
么它就会这样做，并且该类中有很多代码确保在有利的地方缓存内容。本介绍的最后一部分是提到，在获得线性系统之后，使用迭代求解器对其进行求解，然
后进行后处理:我们使用DataOut类创建一个输出文件，然后可以使用一种常见的可视化程序对其进行可视化。\\
请注意

前面对任何有限元实现的所有重要步骤的概述都有对应的处理。II:这个库可以很自然地分成许多“模块”，这些模块涵盖了刚刚概述的基本概念。您可以通过本
页顶部的选项卡访问这些模块\\

求解线性系统\\

对于一个有限元程序，我们最终得到的线性系统相对较小:矩阵的大小为1089×1089，因为我们使用的网格是32×32，因此网格中有332=1089个顶点。在许多后
来的教程程序中，矩阵大小在数万到数十万之间的情况并不少见，并且像ASPECT这样的代码是基于deal构建的。II，我们经常用超过1亿个方程来解决问题
(尽管使用并行计算机)。在任何情况下，即使是这里的小系统，矩阵也比我们在本科或研究生课程中遇到的要大得多，所以问题来了，我们如何求解这样的线性系统。\\



解决线性系统的第一种典型方法是高斯消去法。这种方法的问题在于它需要的操作次数与N3成正比，其中N是线性系统中方程或未知数的数量更具体地说，操作次数
是23N3，上下浮动。当N=1089时，这意味着我们需要进行大约8.61亿次操作。这是一个非常可行的数字，现代处理器只需不到0.1秒的时间就能完成。但很明显，
这是不可能的:如果我们在线性系统中有20倍的方程(也就是说，20倍的未知数)，那么它已经需要1000-10,000秒，或者大约一个小时。将线性系统再扩大十倍，
很明显，我们无法在一台计算机上再解决它。我们可以通过认识到矩阵中只有相对少量的条目是非零的——也就是说，矩阵是稀疏的——来挽救这种情况。高斯消去的
变化可以利用这一点，使过程大大加快;我们将在第29步中首次使用一个这样的方法——在SparseDirectUMFPACK类中实现——在接下来的几个方法中。这些高斯消
去法的变化可能会使我们的问题规模达到10万或20万的量级，但超出这个量级的问题就不多了。\\

相反，我们在这里要做的是采用1952年的一个想法:共轭梯度法，或简称“CG”。CG是一种“迭代”解算器，因为它形成了收敛于精确解的向量序列;事实上，在没有舍
入误差的情况下，经过N次这样的迭代后，如果矩阵是对称且正定的，它就会找到精确解。该方法最初是作为精确求解线性系统的另一种方法而开发的，就像高斯消去
法一样，但它几乎没有什么优点，几十年来基本上被遗忘了。但是，当计算机变得足够强大，可以解决高斯消去法不再有效的问题时(20世纪80年代的某个时候)，CG
被重新发现，因为人们意识到它非常适合于像我们从有限元方法中得到的大型稀疏系统。这是因为(i)它计算的向量收敛于精确的解，因此我们实际上不需要做所有的N
次迭代来找到精确的解，只要我们对相当好的近似感到满意;(ii)它只需要矩阵与向量的乘积，这对于稀疏矩阵非常有用，因为根据定义，稀疏矩阵只有O(N)个元素，
所以矩阵与向量的乘积可以用O(N)个努力来完成，而对于密集矩阵来说，做同样的事情需要花费$N^2$个操作。因此，我们可以希望用最多$O(N^2)$个操作来求解线性系统，
在许多情况下，操作要少得多。\\



因此，有限元代码几乎总是使用迭代求解器，如CG来求解线性系统，我们也将在本代码中这样做。(我们注意到CG方法只适用于对称的正定矩阵;对于其他方程，矩阵
可能不具有这些属性，我们将不得不使用适用于更一般矩阵的迭代求解器的其他变体，如BiCGStab或GMRES。)\\



这些迭代求解器的一个重要组成部分是我们指定我们想要求解线性系统的容差-本质上，关于我们愿意在近似解中接受的误差的声明。线性系统Ax=b的精确解x的近似解
$\overset{\sim}{x}$的误差被定义为$\|x−\overset{\sim}{x}\|$，但这是一个我们无法计算的量，因为我们不知道确切的解x。相反，我们通常认为残差，定义为$\|b−A\overset{\sim}{x}\|=\|A(x-\overset{\sim}{x})\|$，是一个可计算的
测度。然后让迭代求解器计算出越来越精确的解$\overset{\sim}{x}$，直到$\|b−A\overset{\sim}{x}\|\le\tau$。一个实际的问题是$\tau$的值应该是多少。在大多数应用程序中，设置
\[
\tau = 10^(-6)\|b\|    
\]

是一个合理的选择。我们使$\tau$与b的大小(范数)成正比的事实确保了我们对解的精度的期望是相对于解的大小的。这就说得通了，如果我们让右边的b大10倍，那么Ax=b
的解x也会大10倍，$\overset{\sim}{x}$也会;我们希望$\overset{\sim}{x}$中的精确位数与之前相同，这意味着我们也应该在残差$\|b−A\overset{\sim}{x}\|$是原始大小的十倍时终止——如果我们使$\tau$与$\|b\|$成比例，这正是
我们得到的结果。所有这些都将在程序中的Step3::solve()函数中实现。正如您将看到的，用deal设置线性求解器非常简单。整个函数只有三行\\

组合矩阵和右边的向量\\

现在我们知道我们需要什么(即:保存矩阵和向量的对象，以及计算Aij,Fi的方法)，我们可以看看实现这一目标需要什么:
A的对象类型为SparseMatrix，而U和F的对象类型为Vector。我们将在下面的程序中看到用于求解线性系统的类。我们需要一种形成积分的方法。
在有限元法中，最常用的方法是使用正交，即将积分替换为每个单元上的一组正交点的加权和。也就是说，我们首先把Ω上的积分分解成对所有单元格的积分\\

\[
A_(ij) = (\nabla\varphi_i,\nabla\varphi_j) =\sum_{K\in T} \int\nabla_K\nabla\varphi\cdot\nabla\varphi
F_{i} =(\varphi_i,f) =\sum_{K\in T}\int _K\varphi_i f
\]
然后用正交法近似每个细胞的贡献:\\
\[ A_(ij)^k=\int _K\nabla\varphi_i\cdot\nabla\varphi_j\approx\sum_q\nabla\varphi_i(x^k_Q)\cdot\nabla\varphi_j(x_q^K)w_q^k
   F_i^k =\int _K\varphi_if \approx\sum_i(x_q^k)f(x_q^k)w_q^k,\]

式中$T\approx \Omega$为近似域的三角剖分，xKq为单元格K上的第q个正交点，wKq为第q个正交权值。这样做需要不同的部分，下面我们将依次讨论它们。
首先，我们需要一种方法来描述交点的位置$x^K_q$和它们的权重$w^K_q$。它们通常以与形状函数相同的方式从参考单元映射，即隐式地使用MappingQ1类，或者，
如果您显式地这样说，通过从Mapping派生的其他类之一进行映射。参考单元格上的位置和权重由派生自Quadrature基类的对象描述。通常，人们选择一个
正交公式(即一组点和权值)，使正交恰好等于矩阵中的积分;这可以实现，因为积分中的所有因子都是多项式，并且是通过高斯正交公式完成的，在QGauss类
中实现。然后我们需要一些可以帮助我们评估$φ_i(x^K_q)$细胞K .这是什么FEValues类:需要一个有限元对象描述$φ$参比电池,交对象来描述正交分和权重,和一
个映射对象(或隐式地将MappingQ1类),并提供价值和衍生品对实体单元形状函数的K以及各种集成所需的其他信息,在正交分位于K。\\


计算矩阵和右手边作为对所有单元求和(然后对正交点求和)的过程通常称为线性系统的组装，或简称组装，使用与装配线相关的单词的含义，意思是“将一组碎片，片
段或元素组合在一起的行为”。fvalues实际上是汇编过程中的中心类。你可以这样看待它:有限元素及其派生类描述形状函数，即无限维对象:函数在每个点上都有值。
理论上我们需要这个因为我们想用函数上的积分进行分析。然而，对于计算机来说，这是一个非常困难的概念，因为它们通常只能处理有限数量的信息，因此我们通过
使用在参考单元(正交对象)上定义的点映射(映射对象)到实际单元上的点来获得对正交点的求和来替换积分。从本质上讲，我们将问题简化为只需要有限数量的信息，
即形状函数值和导数，正交权值，法向量等，只在有限的一组点上。fvalues类将这三个组件组合在一起，并提供关于特定单元格k的有限信息集。当我们组装下面的
线性系统时，您将看到它的作用。值得注意的是，如果您只是在应用程序中自己创建这三个对象，并自己处理信息，也可以实现所有这些目标。然而，这既不会更简单
(fevalvalues类提供了你真正需要的那种信息)，也不会更快:fevalvalues类被高度优化，只在每个单元格上计算你需要的特定信息;如果可以重用前一个单元格中
的任何内容，那么它就会这样做，并且该类中有很多代码确保在有利的地方缓存内容。本介绍的最后一部分是提到，在获得线性系统之后，使用迭代求解器对其进行求解
，然后进行后处理:我们使用DataOut类创建一个输出文件，然后可以使用一种常见的可视化程序对其进行可视化。请注意前面对任何有限元实现的所有重要步骤的概述
都有对应的处理。II:这个库可以很自然地分成许多“模块”，这些模块涵盖了刚刚概述的基本概念。您可以通过本页顶部的选项卡访问这些模块。最基本的概念组的概述
也可在交易的首页。\\

求解线性系统\\
对于一个有限元程序，我们最终得到的线性系统相对较小:矩阵的大小为1089x1089，因为我们使用的网格是32×32，因此网格中有332=1089个顶点。在许多后来的教程
程序中，矩阵大小在数万到数十万之间的情况并不少见，并且像ASPECT这样的代码是基于deal构建的。II，我们经常用超过1亿个方程来解决问题(尽管使用并行计算机)
。在任何情况下，即使是这里的小系统，矩阵也比我们在本科或研究生课程中遇到的要大得多，所以问题来了，我们如何求解这样的线性系统。解决线性系统的第一种典型
方法是高斯消去法。这种方法的问题在于它需要的操作次数与$N^3$成正比，其中N是线性系统中方程或未知数的数量更具体地说，操作次数是$\frac{2}{3}N_3$，上下浮动。当N=1089时
，这意味着我们需要进行大约8.61亿次操作。这是一个非常可行的数字，现代处理器只需不到0.1秒的时间就能完成。但很明显，这是不可能的:如果我们在线性系统中有20
倍的方程(也就是说，20倍的未知数)，那么它已经需要1000-10,000秒，或者大约一个小时。将线性系统再扩大十倍，很明显，我们无法在一台计算机上再解决它。我们
可以通过认识到矩阵中只有相对少量的条目是非零的——也就是说，矩阵是稀疏的——来挽救这种情况。高斯消去的变化可以利用这一点，使过程大大加快;我们将在第29步中
首次使用一个这样的方法——在SparseDirectUMFPACK类中实现——在接下来的几个方法中。这些高斯消去法的变化可能会使我们的问题规模达到10万或20万的量级，但超
出这个量级的问题就不多了。相反，我们在这里要做的是采用1952年的一个想法:共轭梯度法，或简称“CG”。CG是一种“迭代”解算器，因为它形成了收敛于精确解的向量序
列;事实上，在没有舍入误差的情况下，经过N次这样的迭代后，如果矩阵是对称且正定的，它就会找到精确解。该方法最初是作为精确求解线性系统的另一种方法而开发的，
就像高斯消去法一样，但它几乎没有什么优点，几十年来基本上被遗忘了。但是，当计算机变得足够强大，可以解决高斯消去法不再有效的问题时(20世纪80年代的某个时候)
，CG被重新发现，因为人们意识到它非常适合于像我们从有限元方法中得到的大型稀疏系统。这是因为(i)它计算的向量收敛于精确的解，因此我们实际上不需要做所有的N次
迭代来找到精确的解，只要我们对相当好的近似感到满意;(ii)它只需要矩阵与向量的乘积，这对于稀疏矩阵非常有用，因为根据定义，稀疏矩阵只有O(N)个元素，所以矩阵与
向量的乘积可以用O(N)个努力来完成，而对于密集矩阵来说，做同样的事情需要花费$N^2$个操作。因此，我们可以希望用最多$O(N^2)$个操作来求解线性系统，在许多情况下，
操作要少得多。因此，有限元代码几乎总是使用迭代求解器，如CG来求解线性系统，我们也将在本代码中这样做。(我们注意到CG方法只适用于对称的正定矩阵;对于其他方程
，矩阵可能不具有这些属性，我们将不得不使用适用于更一般矩阵的迭代求解器的其他变体，如BiCGStab或GMRES。)这些迭代求解器的一个重要组成部分是我们指定我们想要求
解线性系统的容差-本质上，关于我们愿意在近似解中接受的误差的声明。线性系统Ax=b的精确解x的近似解$\overset{\sim}{x}$的误差被定义为$\|x-\overset{\sim}{x}\|$，但这是一个我们无法计算的量，因为我
们不知道确切的解x。相反，我们通常认为残差，定义为$\|b−A\overset{\sim}{x}\|=\|a (x−\overset{\sim}{x})\|$，是一个可计算的测度。然后让迭代求解器计算出越来越精确的解$\overset{\sim}{x}$，直到$\|b−A\overset{\sim}{x}\| \le \tau$。一个实际
的问题是$\tau$的值应该是多少。在大多数应用程序中，设置\\

\[
\tau = 10^{-6}\|b\|
\]
是一个合理的选择。我们使$\tau$与b的大小(范数)成正比的事实确保了我们对解的精度的期望是相对于解的大小的。这就说得通了，如果我们让右边的b大10倍，那么Ax=b的
解x也会大10倍，$\overset{\sim}{x}$也会;我们希望$\overset{\sim}{x}$中的精确位数与之前相同，这意味着我们也应该在残差$\|b−A\overset{\sim}{x}\|$是原始大小的
十倍时终止——如果我们使$\tau$与$\|b\|$成比例，这正是我们得到的结果。所有这些都将在程序中的Step3::solve()函数中实现。正如您将看到的，用deal设置线性求解器非常简单。
整个函数只有三行。\\

关于实现\\
虽然这是使用有限元方法可以求解的最简单的方程，但该程序显示了大多数有限元程序的基本结构，并作为几乎所有后续程序都将遵循的模板。具体来说，这个程序的主类是这样的:

\begin{verbatim}
    class Step3
    {
      public:
        Step3 ();
        void run ();
     
      private:
        void make_grid ();
        void setup_system ();
        void assemble_system ();
        void solve ();
        void output_results () const;
     
        Triangulation<2>     triangulation;
        FE_Q<2>              fe;
        DoFHandler<2>        dof_handler;
     
        SparsityPattern      sparsity_pattern;
        SparseMatrix<double> system_matrix;
        Vector<double>       solution;
        Vector<double>       system_rhs;
\end{verbatim}
这遵循了面向对象编程的数据封装咒语，也就是说，我们尽最大努力将这个类的几乎所有内部细节隐藏在外部无法访问的私有成员中。
让我们从成员变量开始:它们遵循我们在上面的要点中概述的构建块，即我们需要一个Triangulation和一个DoFHandler对象，以及一个描述我们想要使用的
各种形状函数的有限元对象。第二组对象与线性代数有关:系统矩阵和右侧以及解向量，以及描述矩阵稀疏模式的对象。这就是本课程所需要的(也是任何固定PDE
解算器所需要的基本要素)，并且需要在整个程序中生存。与此相反，汇编所需的fvalues对象仅在整个汇编过程中才需要，因此我们在执行该操作的函数中将其
创建为局部对象，并在其结束时再次销毁它。\\
其次，让我们看看成员函数。这些，也已经形成了几乎所有以下教程程序将使用的共同结构:\\
make\_grid():这是一个预处理函数。顾名思义，它设置存储三角测量的对象。在后面的例子中，它还可以处理边界条件、几何图形等。
setup\_system():在这个函数中设置了解决问题所需的所有其他数据结构。特别是，它将初始化DoFHandler对象并正确地调整与线性代数有关的各种对象的
大小。这个函数通常与上面的预处理函数分开，因为在一个时间相关的程序中，当网格自适应细化时，它可能至少每隔几个时间步调用一次(我们将在步骤6中看
到如何做)。另一方面，在上面的预处理函数中设置网格本身只在程序开始时完成一次，因此，将其分离到自己的函数中。assemble\_system():这是计算矩阵
和右侧内容的地方，如上面的介绍中详细讨论的那样。由于处理这个线性系统在概念上和计算它的项是非常不同的，我们把它和下面的函数分开。
solve():这就是我们计算线性系统AU=F的解U的函数。在当前的程序中，这是一个简单的任务，因为矩阵非常简单，但是当问题不再那么简单时，它将成为程序
大小的重要组成部分(例如，一旦您对库有了更多的了解，请参阅步骤20、步骤22或步骤31)。output\_results():最后，当您计算出一个解决方案时，您可能
想要对它做一些事情。例如，您可能希望以一种可以可视化的格式输出它，或者您可能希望计算您感兴趣的量:例如，热交换器中的热流，机翼的空气摩擦系数，
最大桥载，或仅仅是某一点的数值解的值。因此，这个函数是对您的解决方案进行后处理的地方。所有这些都由一个公共函数(而不是构造函数)，即run()函数来
完成。它是从创建该类型对象的地方调用的，并且它以适当的顺序调用所有其他函数。将此操作封装到run()函数中，而不是从main()调用所有其他函数，可以确保
您可以更改该类中关注点分离的实现方式。例如，如果其中一个函数变得太大，您可以将其拆分为两个，并且您唯一需要关注的更改结果是在同一个类中，而不是在其
他任何地方。如上所述，您将在下面的许多教程程序中再次看到这种一般结构——有时函数名的拼写有变化，但本质上是功能分离的顺序。\\

关于类型的说明\\
交易。II通过命名空间类型中的别名定义了许多整型。(在前面的句子中，“integral”这个词被用作形容词，对应于名词“integer”。它不应该与表示曲线或曲面
下的面积或体积的名词“积分”混淆。形容词“积分”广泛用于c++世界环境如“积分式”、“积分常数”,特别是等等),在这个程序中你会看到类型::global\_dof\_index
在两个地方:一个整数类型,用于表示全球指数的自由度,也就是说,该指数中的一个特定的自由度DoFHandler对象上定义的三角(相对于特定的自由度指数在一个特定的细胞)
。对于当前程序(以及几乎所有教程程序)，您将拥有数千到数百万个全局未知数(并且，对于$Q_1$元素，您将在2d和3d中分别拥有4个局部单元格和8个局部单元格)。
因此，允许为全局DoF索引存储足够大的数字的数据类型是unsigned int，因为它允许存储介于0到略大于40亿的数字(在大多数系统上，整数是32位的)。实际上，
types::global\_dof\_index就是这样。\\
那么，为什么不直接使用unsigned int呢?交易。在7.3版本之前，我一直这样做。然而,交易。II支持非常大的计算(通过步骤40中讨论的框架)，当分布在几千个处理器
上时，可能有超过40亿个未知数。因此，在unsigned int不够大的情况下，我们需要64位unsigned整型。为了实现这一点，我们引入了types::global\_dof\_index，
默认情况下它被定义为简单的unsigned int，而如果有必要，可以通过在配置过程中传递一个特定的标志来将其定义为unsigned long long int(请参阅ReadMe文件)。
这涵盖了技术方面。但是还有一个文档目的:在库和构建在库上的代码的任何地方，如果您看到使用数据类型types::global\_dof\_index的地方，您立即知道正在引用的数
量实际上是一个全局dof索引。如果我们只使用unsigned int(也可以是局部索引、边界指示符、材料id等)，则没有明显的意义。立即知道变量引用的内容也有助于避免
错误:很明显，如果您看到types::global\_dof\_index类型的对象被分配给types::subdomain\_id类型的变量，即使它们都是由无符号整数表示的，编译器也不会因此
报错。\\

在更实际的术语中，这种类型的存在意味着在组装期间，我们创建一个4×4矩阵(在2d中，使用Q1元素)我们当前所处的单元的贡献，然后我们需要将这个矩阵的元素添加到全局
(系统)矩阵的适当元素中。为此，我们需要获得当前单元局部自由度的全局指标，为此我们将始终使用以下代码片段:\\
\begin{verbatim}
    cell->get_dof_indices (local_dof_indices);

\end{verbatim}
其中局部自由度指数声明为\\
\begin{verbatim}
    std::vector<types::global_dof_index> local_dof_indices (fe.n_dofs_per_cell());

\end{verbatim}

这个变量的名称可能有点用词不当——它代表“在当前单元格上局部定义的自由度的全局索引”——但是保存此信息的变量在整个库中都是这样命名的。\\

请注意\\

Types::global\_dof\_index不是该命名空间中定义的唯一类型。相反，有一个完整的家族，包括types::subdomain\_id、types::boundary\_id和types::material\_id。
所有这些都是整数数据类型的别名，但是，正如上面所解释的，它们在整个标准库中使用，这样(i)变量的意图变得更容易识别，(ii)如果有必要，可以将实际类型更改为更大的类型，
而不必遍历整个标准库并找出unsigned int的特定用法是否对应于，比如说，一个材料指示符。\\
\begin{figure}[htbp]
    \centering
    \includegraphics{solution.eps}
    \caption{输出结果}

\end{figure}

\section{小结}
解决的问题：\\
step-3是deal.II中的第一个拉普拉斯求解器。这个例程将会介绍有限元程序的一般结构，展示了如何组装线性系统，如何解这个线性方程，然后从中生成图形输出源文件
（用于后处理）。step-3是deal.II中的第一个拉普拉斯求解器。这个例程将会介绍有限元程序的一般结构，展示了如何组装线性系统，如何解这个线性方程，然后从中生
成图形输出源文件（用于后处理）。\\


% 使用 BibTeX 管理文献引用$
\bibliographystyle{plain}
\bibliography{references}

\end{document}